from abc import ABC
from typing import Any, Optional, Generic, TypeVar
from sqlalchemy import ColumnElement, insert, select, update, delete
from sqlalchemy.ext.asyncio import AsyncSession

Model = TypeVar("Model")


class AbstractRepository(Generic[Model], ABC):
    model: type = None

    def __init__(self, session: AsyncSession) -> None:
        self._session = session

    async def insert_one_with_data(self, **data: Any) -> int:
        stmt = insert(self.model).values(**data).returning(self.model.id)
        res = await self._session.execute(stmt)
        await self._session.commit()
        return res.scalar_one()

    async def select_one_where(self, where: ColumnElement) -> Optional[Model]:
        stmt = select(self.model).where(where)
        res = await self._session.execute(stmt)
        return res.scalar_one_or_none()

    async def select_many_where(self, where: ColumnElement) -> list[Model]:
        stmt = select(self.model).where(where)
        res = await self._session.execute(stmt)
        return list(res.scalars().all())

    async def select_by_id(self, pk: int) -> Optional[Model]:
        return await self.select_one_where(self.model.id == pk)

    async def select_all(self) -> list[Model]:
        stmt = select(self.model)
        res = await self._session.execute(stmt)
        return list(res.scalars().all())

    async def edit_all(self, **data: Any) -> None:
        stmt = update(self.model).values(**data)
        await self._session.execute(stmt)
        await self._session.commit()
        return

    async def edit_where(self, where: ColumnElement, **data: Any) -> None:
        stmt = update(self.model).where(where).values(**data)
        await self._session.execute(stmt)
        await self._session.commit()
        return

    async def edit_by_id(self, pk: int, **data: Any) -> None:
        await self.edit_where(self.model.id == pk, **data)
        return

    async def delete_where(self, where: ColumnElement) -> None:
        stmt = delete(self.model).where(where)
        await self._session.execute(stmt)
        await self._session.commit()
        return
